package org.jenkinsci.plugins.p4scm;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.CheckForNull;

import org.jenkinsci.plugins.p4scm.utils.CommonUtil;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

import com.tek42.perforce.Depot;
import com.tek42.perforce.PerforceException;
import com.tek42.perforce.model.Label;

import hudson.model.AbstractBuild;
import hudson.model.Run;
import hudson.scm.AbstractScmTagAction;
import hudson.scm.SCM;
import hudson.security.Permission;
import hudson.util.FormValidation;
import static hudson.Util.fixNull;

@ExportedBean
public class P4TagAction extends AbstractScmTagAction {

    private final int changeNumber;
    private Depot depot;
    private List<PerforceTag> tags = new ArrayList<PerforceTag>();
    private String view;
    private String owner;
    
    public P4TagAction(P4TagAction tga) {
        super((tga.getRun()));
        this.changeNumber = tga.changeNumber;
        this.depot = tga.depot;
        this.tags.addAll(tga.tags);
        this.view = tga.view;
        this.owner = tga.owner;
    }
    
    public P4TagAction(Run<?,?> build, Depot depot, int changeNumber, String views, String owner) {
        super(build);
        this.depot = depot;
        this.changeNumber = changeNumber;
        this.view = views;
        this.owner = owner;
    }
    
    public P4TagAction(AbstractBuild<?,?> build, Depot depot, int changeNumber, String views, String owner) {
        this((Run<?,?>)build, depot, changeNumber, views, owner);
    }
    
    
    public Depot getDepot() {
        return depot;
    }

    public void setDepot(Depot depot) {
        this.depot = depot;
    }

    public List<PerforceTag> getTags() {
        return tags;
    }

    public void setTags(List<PerforceTag> tags) {
        this.tags = tags;
    }

    public String getView() {
        return view;
    }

    public void setView(String view) {
        this.view = view;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    @Exported
    public int getChangeNumber() {
        return changeNumber;
    }

    public static class PerforceTag {
        private String name;
        private String desc;

        public PerforceTag(String name, String desc) {
            this.name = name;
            this.desc = desc;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
        
    }
    
    @Override
    public String getDisplayName() {
        if (isTagged())
            return "Perforce Label";
        else
            return "Label This Build";
    }

    /**
     * Returns true if this build has already been tagged at least once.
     */
    @Override
    public boolean isTagged() {
        return tags != null && !tags.isEmpty();
    }

    @Override
    public String getIconFileName() {
        if (!getACL().hasPermission(getPermission()))
            return null;
        return "save.gif";
    }
    
    /**
     * Checks to see if the user entered tag matches any Perforce restrictions.
     */
    public String isInvalidTag(String tag) {
        Pattern spaces = Pattern.compile("\\s{1,}");
        Matcher m = spaces.matcher(tag);
        if (m.find()) {
            return "Spaces are not allowed.";
        }
        return null;
    }
    
    @CheckForNull
    public P4SCM getSCM() {
        Run<?,?> run = getRun();
        if(run instanceof AbstractBuild) {
            AbstractBuild<?, ?> build = (AbstractBuild<?,?>)run;
            SCM scm = build.getProject().getScm();
            if(scm instanceof P4SCM){
                return (P4SCM)scm;
            }
        }
        return null;
    }

    @Override
    protected Permission getPermission() {
        return P4SCM.TAG;
    }
    
    /**
     * Checks if the value is a valid Perforce tag (label) name.
     */
    public synchronized FormValidation doCheckTag(@QueryParameter String value) {
        value = fixNull(value).trim();
        String error = value.length() > 0 ? isInvalidTag(value) : null;
        if (error != null) return FormValidation.error(error);
        return FormValidation.ok();
    }

    /**
     * Invoked to actually tag the workspace.
     */
    public synchronized void doSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, InterruptedException {
        getACL().checkPermission(getPermission());

        String tag = req.getParameter("name");
        String desc = req.getParameter("desc");
        String owner = req.getParameter("owner");

        tagBuild(tag, desc, owner);

        rsp.sendRedirect(".");
    }

    public void tagBuild(String tagname, String description, String owner) throws IOException, InterruptedException {
        AbstractBuild<?, ?> build = (AbstractBuild<?,?>)getRun();
        
        Label label = new Label();
        label.setName(tagname);
        label.setDescription(description);
        label.setRevision(new Integer(changeNumber).toString());
        if(owner!=null && !owner.equals("")) label.setOwner(owner);

        P4SCM scm = getSCM();
        if(scm != null){
            depot.setPassword(scm.getDecryptedP4Passwd(build.getProject(), build.getBuiltOn()));
        }

        //Only take the depot paths and add them to the view.
        List<String> viewPairs = CommonUtil.parseProjectPath(view, "workspace");
        for (int i=0; i < viewPairs.size(); i+=2){
            String depotPath = viewPairs.get(i);
            label.addView(depotPath);
        }
        try {
            depot.getLabels().saveLabel(label);
        } catch(PerforceException e) {
            e.printStackTrace();
            throw new IOException("Failed to issue perforce label." + e.getMessage());
        }
        tags.add(new PerforceTag(tagname,description));
        build.save();
    }
}
